﻿#region Using References

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using Beaker.OpenCube.CubeManager.Model.Interfaces;

#endregion

namespace Beaker.OpenCube.CubeManager.Model.Framework
{
	/// <summary>
	/// Abstract base class for an entity
	/// </summary>
	public abstract class Entity
		: IIdentifiable
	{
		protected Entity()
		{
			Id = Guid.NewGuid();
		}

		/// <summary>
		/// Gets or sets the id of the entity.
		/// </summary>
		/// <value>
		/// The id of the entity.
		/// </value>
		public Guid Id { get; internal set; }

		/// <summary>
		/// Sets the backingstore value if changed, and syncs the other side of the 1-n relation.
		/// </summary>
		/// <typeparam name="TParent">The type of the parent entity.</typeparam>
		/// <typeparam name="TEntity">The type of the child entity.</typeparam>
		/// <param name="backingStore">The backing store of the reference to the parent.</param>
		/// <param name="newValue">The new parent.</param>
		/// <param name="getSetFromParentEntity">Function to get the set with children from the parent.</param>
		protected void SetAndSync<TParent, TEntity>( ref TParent backingStore, TParent newValue, Func<TParent, ISet<TEntity>> getSetFromParentEntity )
			where TEntity : Entity
			where TParent : Entity
		{
			Contract.Requires<ArgumentNullException>( getSetFromParentEntity != null );

			if( !EqualityComparer<TParent>.Default.Equals( backingStore, newValue ) )
			{
				TParent oldValue = backingStore;
				backingStore = newValue;
				UpdateSets( getSetFromParentEntity, oldValue, newValue );
			}
		}

		/// <summary>
		/// Updates the sets.
		/// </summary>
		/// <typeparam name="TParent">The type of the parent entity.</typeparam>
		/// <typeparam name="TEntity">The type of the child entity.</typeparam>
		/// <param name="getSetFromParentEntity">Function to get the set with children from the parent.</param>
		/// <param name="oldValue">The old parent entity.</param>
		/// <param name="newValue">The new parent entity.</param>
		/// <exception cref="System.InvalidOperationException">
		/// The Set is not an EntitySet
		/// </exception>
		private void UpdateSets<TParent, TEntity>( Func<TParent, ISet<TEntity>> getSetFromParentEntity, TParent oldValue, TParent newValue )
			where TEntity : Entity
			where TParent : Entity
		{
			// when old and new value are same, (should not occure), then we can leave it at this.
			if( oldValue == newValue )
				return;

			// when oldValue is set, remove it from there
			if( oldValue != null )
			{
				var oldSet = getSetFromParentEntity( oldValue ) as EntitySet<TEntity, TParent>;
				if( oldSet == null )
					throw new InvalidOperationException( "The Set is not an EntitySet" );
				oldSet.Remove( (TEntity)this );
			}

			if( newValue != null )
			{
				var newSet = getSetFromParentEntity( newValue ) as EntitySet<TEntity, TParent>;
				if( newSet == null )
					throw new InvalidOperationException( "The Set is not en EntitySet" );
				newSet.Add( (TEntity)this );
			}
		}
	}
}
